package com.qianfeng.ssmplatform.search.service.impl;


//
//                            _ooOoo_  
//                           o8888888o  
//                           88" . "88  
//                           (| -_- |)  
//                            O\ = /O  
//                        ____/`---'\____  
//                      .   ' \\| |// `.  
//                       / \\||| : |||// \  
//                     / _||||| -:- |||||- \  
//                       | | \\\ - /// | |  
//                     | \_| ''\---/'' | |  
//                      \ .-\__ `-` ___/-. /  
//                   ___`. .' /--.--\ `. . __  
//                ."" '< `.___\_<|>_/___.' >'"".  
//               | | : `- \`.;`\ _ /`;.`/ - ` : | |  
//                 \ \ `-. \_ __\ /__ _/ .-` / /  
//         ======`-.____`-.___\_____/___.-`____.-'======  
//                            `=---='  
//  
//         .............................................  
//                  佛祖镇楼            BUG辟易  
//          佛曰:  
//                  写字楼里写字间，写字间里程序员；  
//                  程序人员写程序，又拿程序换酒钱。  
//                  酒醒只在网上坐，酒醉还来网下眠；  
//                  酒醉酒醒日复日，网上网下年复年。  
//                  但愿老死电脑间，不愿鞠躬老板前；  
//                  奔驰宝马贵者趣，公交自行程序员。  
//                  别人笑我忒疯癫，我笑自己命太贱；  


import com.fasterxml.jackson.databind.ObjectMapper;
import com.qianfeng.smsplatform.common.model.Standard_Submit;
import com.qianfeng.ssmplatform.search.cache.ParamsLocalCache;
import com.qianfeng.ssmplatform.search.service.DocService;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Created by jackiechan on 2021/7/22 11:04
 *
 * @author jackiechan
 * 人学始知道，不学非自然。
 */
@Service
public class DocServiceImpl implements DocService {
    @Value("${elasticsearch.index.name}")
    private String index;


    private ParamsLocalCache paramsLocalCache;

    @Autowired
    public void setParamsLocalCache(ParamsLocalCache paramsLocalCache) {
        this.paramsLocalCache = paramsLocalCache;
    }

    private ObjectMapper objectMapper;

    @Autowired
    public void setObjectMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
        //TODO 我们在这里单独设置日期格式化而不是直接给目标类上面添加jsonformat注解的原因是
        // 因为直接加在目标类上面会导致所有依赖目标类的项目都要添加jackson依赖,而我们有些模块是不需要jackson
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        objectMapper.setDateFormat(simpleDateFormat);
    }

    private RestHighLevelClient highLevelClient;

    @Autowired
    public void setHighLevelClient(RestHighLevelClient highLevelClient) {
        this.highLevelClient = highLevelClient;
    }

    @Override
    public void addDoc(Standard_Submit standard_submit) throws IOException {
        IndexRequest request = new IndexRequest(index);//设置要操作的库
        //request.source(standard_submit);
        String json = objectMapper.writeValueAsString(standard_submit);
        request.source(json, XContentType.JSON);
        highLevelClient.index(request, RequestOptions.DEFAULT);
    }

    @Override
    public void updateDoc(Standard_Submit standard_submit) throws IOException {
        UpdateRequest updateRequest = new UpdateRequest();
        updateRequest.index(index);
        Map targetData = queryDataByCidAndPhoneAndSrcId(standard_submit.getClientID(), standard_submit.getSrcSequenceId(), standard_submit.getDestMobile());

        if (targetData != null) {//如果能查询到当前的数据,则进行更新
            Object id = targetData.get("_id");
            updateRequest.id((String) id);
            updateRequest.doc(objectMapper.writeValueAsString(standard_submit), XContentType.JSON);
            highLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        }
//        updateRequest.do
//
//        highLevelClient.update()
    }

    @Override
    public Map queryDataByCidAndPhoneAndSrcId(int clientId, long srcId, String phoneNum) throws IOException {
        SearchRequest searchRequest = new SearchRequest(index);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        searchSourceBuilder.query(QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery("clientID", clientId))
                .must(QueryBuilders.termQuery("srcSequenceId", srcId))
                .must(QueryBuilders.termQuery("destMobile", phoneNum)));

        searchRequest.source(searchSourceBuilder);

        SearchResponse searchResponse = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHit[] searchHits = searchResponse.getHits().getHits();
        if (searchHits != null) {
            for (SearchHit searchHit : searchHits) {

                Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
                sourceAsMap.put("_id", searchHit.getId());//因为source中是不包含id的,所以我们单独获取一下id设置进去
                System.err.println(sourceAsMap);
                return sourceAsMap;
            }
        }
        return null;
    }

    /**
     * @param params 用户传递的参数,会根据不同的需求传递不同的参数过来
     * @return
     */
    @Override
    public List<Map> queryDataByMultiCondition(Map<String, String> params) throws IOException {
        //要求： 用户传递再多的参数也没用，只能按照我们要去的参数去查询
        //但是允许用户传递分页，高亮参数过来
        SearchRequest searchRequest = new SearchRequest(index);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();//因为查询条件多， 所以是一个bool的查询
        int from = 0;
        int size = 2;
        //获取用户传递的分页参数， 但是用户可能没有传递， 没有传递的情况下我们使用默认值
        String fromString = params.remove("from");
        String sizeString = params.remove("size");
        if (StringUtils.hasText(fromString)) {
            from = Integer.parseInt(fromString);
        }
        if (StringUtils.hasText(sizeString)) {
            size = Integer.parseInt(sizeString);
        }
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(size);


        //高亮,用户可以传递高亮参数过来，我们允许用户通过highlight这个参数来传递一个高亮的列名
        //允许用户自定义高亮的样式
        boolean isEnabelHighLight = false;
        String highlight = params.remove("highlight");//获取到用户传递的高亮的列名，但是和我们es中实际的列是不一样的
        System.err.println("用户传递的期望的高亮的列：" + highlight);

        //获取到我们所有允许的条件
        //经过测试，我们发现了一个问题，用户没有传递任何我们允许传递的条件，就把所有的数据都查出来了，而且万一用户传递了某些条件，但是不是任何我们允许返回了所有数据就和用户期望的也不一样
        //定义规则，我们允许的条件是用户必须传递的条件还是可以选的条件
        Map<String, Map<String, Object>> paramsMap = paramsLocalCache.getParamsMap();
        for (Map.Entry<String, Map<String, Object>> entry : paramsMap.entrySet()) {

            String key = entry.getKey();//我们允许的参数名
            Map<String, Object> value = entry.getValue();//我们允许传递的参数的对应的其他信息，比如实际查询的列，查询类型是什么
            String param = params.get(key);//用户实际传递的当前参数的值
            if (key.equalsIgnoreCase(highlight)) {
                //用户传递的高亮的参数名和我们当前正在遍历的参数名保持一致，我们需要替换为真是的高亮列名
                highlight = (String) value.get("cloum");//替换为实际的值
                System.err.println("我们替换后的高亮的列：" + highlight);
            }
            if (StringUtils.hasText(param)) {
                //用户传递了我们允许的参数
                //设置查询条件，查询的具体列是哪个？ 什么类型的查询
                String cloum = (String) value.get("cloum");//实际允许的列
                String type = (String) value.get("type");//实际允许的类型

                switch (type) {
                    case "match":
                        MatchQueryBuilder matchQuery = QueryBuilders.matchQuery(cloum, param);
                        boolQueryBuilder.must(matchQuery);
                        break;
                    case "term":

                        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(cloum, param);
                        boolQueryBuilder.must(termQueryBuilder);
                        break;
                    case "range":
                        Integer order = (Integer) value.get("tOrder");
                        RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(cloum);
                        if (0 == order) {
                            rangeQuery.from(param);
                        } else {
                            rangeQuery.to(param);
                        }
                        boolQueryBuilder.must(rangeQuery);

                        break;
                }


            } else {
                Integer isNeed = (Integer) value.get("isNeed");
                if (1 == isNeed) {
                    //当前参数是必须传递但是没有传递的
                    System.err.println("没有传递必须传递的参数：" + key);
                    return null;
                }

            }
        }


        if (StringUtils.hasText(highlight)) {
            isEnabelHighLight = true;
            String preTag = params.get("pretag");
            String postTag = params.get("posttag");
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field(highlight, 0).postTags(postTag).preTags(preTag);
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        searchSourceBuilder.query(boolQueryBuilder);
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHit[] hits = searchResponse.getHits().getHits();
//        Arrays.asList(hits).forEach(hit->{
//            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
//        });

        boolean finalIsEnabelHighLight = isEnabelHighLight;
        String finalHighlight = highlight;
        List list = Arrays.stream(hits).map(
                hit -> {
                    Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                    if (finalIsEnabelHighLight) {
                        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                        if (highlightFields != null) {
                            HighlightField highlightField = highlightFields.get(finalHighlight);
                            if (highlightField != null) {
                                Text[] fragments = highlightField.getFragments();
                                if (fragments != null && fragments.length > 0) {
                                    String highLightContent = fragments[0].toString();
                                    sourceAsMap.put(finalHighlight, highLightContent);//用高亮替换内容
                                }
                            }
                        }
                    }

                    return sourceAsMap;
                }
        ).collect(Collectors.toList());
        params.put("resultTotalCount", searchResponse.getHits().getTotalHits().value+"");
        return list;
    }
}
